package com.thinkit.bigdata.core.util;

import com.alibaba.fastjson.JSONObject;
import com.thinkit.bigdata.core.ConfigHelper;
import com.thinkit.bigdata.core.util.httpUtil.HttpUtil;
import com.thinkit.bigdata.web.model.result.HtmlResult;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FtpUtil {

    private static Logger logger = LoggerFactory.getLogger(FtpUtil.class);

    /**
     * FTP 传输使用编码
     */
    private static String ftpEncoding;

    static {
        ftpEncoding = "UTF-8";
    }

    /**
     * FTP 客户端
     */
    private FTPClient ftpClient = new FTPClient();

    /**
     * 根目录
     */
    private static String rootPath = "/";

    /**
     * 路径分隔符
     */
    private static char pathSeparator = '/';

    /**
     * 是否已经成功登录
     */
    private boolean isLogin = false;

    /**
     * 判断FtpUtil
     *
     * @param hostname 服务器IP或域名
     * @param port     端口
     * @param name     登录账号
     * @param password 密码
     */
    public static HtmlResult isFtpUtil(String hostname, int port, String name, String password) {
        FTPClient ftpClient = new FTPClient();
        ftpClient.setAutodetectUTF8(true);
        ftpClient.setControlEncoding(ftpEncoding);
        //FTP 连接超时时间 10秒
        int ftpTimeOut = 10 * 1000;
        ftpClient.setConnectTimeout(ftpTimeOut);
        try {
            ftpClient.connect(hostname, port);
            logger.info("连接FTP[" + hostname + ":" + port + "]");
        } catch (IOException e) {
            logger.error("连接FTP[" + hostname + ":" + port + "]异常", e);
            return HtmlResult.failure("连接FTP[" + hostname + ":" + port + "]异常");
        }
        try {
            if(ftpClient.login(name, password)){
                logger.info("登录FTP[" + name + "账号]");
                //开通文件传输
                ftpClient.enterLocalPassiveMode();
                ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
                ftpClient.disconnect();
                return HtmlResult.success();
            }else{
                logger.error("登录FTP[" + hostname + ":" + port + "]异常，请检查登录名或密码是否错误");
                return HtmlResult.failure("登录FTP[" + hostname + ":" + port + "]异常，请检查登录名或密码是否错误,或者ftp相关配置");
            }
        } catch (IOException e) {
            logger.error("登录FTP[" + hostname + ":" + port + "]异常", e);
            return HtmlResult.failure("登录FTP[" + hostname + ":" + port + "]异常");
        }
    }

    /**
     * 初始化FtpUtil
     *
     * @param hostname 服务器IP或域名
     * @param port     端口
     * @param name     登录账号
     * @param password 密码
     */
    public FtpUtil(String hostname, int port, String name, String password) {
        ftpClient.setAutodetectUTF8(true);
        ftpClient.setControlEncoding(ftpEncoding);
        //FTP 连接超时时间 10秒
        int ftpTimeOut = 10 * 1000;
        ftpClient.setConnectTimeout(ftpTimeOut);
        try {
            ftpClient.connect(hostname, port);
            logger.info("连接FTP[" + hostname + ":" + port + "]");
            isLogin = true;
        } catch (IOException e) {
            logger.error("连接FTP[" + hostname + ":" + port + "]异常", e);
        }
        if(isLogin){
            try {
                isLogin = ftpClient.login(name, password);
                if(isLogin){
                    logger.info("登录FTP[" + name + "账号]");
                    //开通文件传输
                    ftpClient.enterLocalPassiveMode();
                    ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
                }else{
                    logger.error("登录FTP[" + hostname + ":" + port + "]异常，请检查登录名或密码是否错误");
                }
            } catch (IOException e) {
                isLogin = false;
                logger.error("登录FTP[" + hostname + ":" + port + "]异常", e);
            }
        }
    }

    public static boolean sendMobileMes(String rcvMobileStr, String content) {
        // 生成发送post请求的参数
        JSONObject jsonObject = new JSONObject();
        String[] rcvMobiles = rcvMobileStr.split(",");
        jsonObject.put("rcvMobiles", rcvMobiles);
        jsonObject.put("content", content);
        String postParam = jsonObject.toJSONString();
        try {
            // 请求url
            String smsUrl = ConfigHelper.getString("sms.url");
            // 请求返回的结果
            String postResult = HttpUtil.sendJsonHttpPost(smsUrl, postParam);
            logger.info("发送短信验证码返回结果信息" + postResult);
            int code = Integer.parseInt(postResult);
            if (code == 0) return true;
            if (code == -9999) return false;
        } catch (Exception e) {
            logger.error("短信验证码发送过程中出现错误", e);
        }
        return false;
    }

    public static boolean sendCcs(String method, String logTxt) throws IOException {
        Map<String, Object> param = new HashMap<>();
        param.put("systemId", "dxQuality");
        param.put("taskName", method);
        param.put("runType", 2);
        String response = HttpUtil.sendJsonHttpPost(PropertiesUtil.getProperty("sendCronTaskToDispatch"), net.sf.json.JSONObject.fromObject(param).toString());
        logger.info(logTxt + method + "请求接口返回值" + response);
        net.sf.json.JSONObject classify = net.sf.json.JSONObject.fromObject(response);
        int rtnCode = classify.getInt("rtnCode");
        return rtnCode == 0;
    }

    /**
     * 跳至已存在的目录，需要从根目录开始，则需要以"/"开头
     *
     * @param path 服务器目录
     * @return -
     */
    private boolean changToExistDir(String path) {
        if(!isLogin){
            return false;
        }
        String path_ = formatPath(path);
        try {
            if (ftpClient.changeWorkingDirectory(path_)) {
                logger.info("跳至FTP路径[" + path_ + "]成功");
                return true;
            } else {
                logger.info("跳至FTP路径[" + path_ + "]失败");
            }
        } catch (IOException e) {
            logger.error("进入FTP路径[" + path_ + "]异常", e);
        }
        return false;
    }

    /**
     * 创建目录，若目录已存在，则计为创建成功
     *
     * @param path -
     * @return -
     */
    public boolean createDirs(String path) {
        if(!isLogin){
            return false;
        }
        boolean b = true;
        String path_ = formatPath(path);
        try {
            if (ftpClient.changeWorkingDirectory(path_)) {
                logger.info("FTP路径[" + path_ + "]已存在");
            } else {
                List<String> noExistPaths = new ArrayList<>();
                String tmp = path_;
                while (!ftpClient.makeDirectory(tmp)) {
                    noExistPaths.add(tmp);
                    tmp = tmp.substring(0, tmp.lastIndexOf(pathSeparator));
                }
                while (noExistPaths.size() > 0) {
                    //有异常设为创建失败
                    if (!ftpClient.makeDirectory(noExistPaths.remove(noExistPaths.size() - 1))) {
                        b = false;
                    }
                }
                if (b) {
                    logger.info("创建FTP路径[" + path_ + "]成功");
                } else {
                    logger.info("创建FTP路径[" + path_ + "]失败");
                }
            }
            changeToRoot();
        } catch (IOException e) {
            logger.error("创建FTP路径[" + path_ + "]异常", e);
        }
        return b;
    }

    /**
     * 跳至目录（若目录不存在，则自动创建目录）
     *
     * @param path 服务器目录
     * @return -
     */
    private boolean changToDir(String path) {
        String path_ = formatPath(path);
        try {
            if (ftpClient.changeWorkingDirectory(path_)) {
                logger.info("跳至FTP路径[" + path_ + "]成功");
                return true;
            } else {
                if (createDirs(path_) && ftpClient.changeWorkingDirectory(path_)) {
                    logger.info("跳至FTP路径[" + path_ + "]成功");
                    return true;
                }
            }
        } catch (IOException e) {
            logger.error("创建FTP路径[" + path_ + "]异常", e);
        }
        logger.info("跳至FTP路径[" + path_ + "]失败");
        return false;
    }

    /**
     * 切换当前目录到根目录
     */
    private void changeToRoot() {
        try {
            if (ftpClient.changeWorkingDirectory(rootPath)) {
                logger.info("跳至FTP根路径成功");
            } else {
                logger.info("跳至FTP根路径失败");
            }
        } catch (IOException e) {
            logger.error("跳至FTP根路径失败", e);
        }
    }

    /**
     * 断开连接
     */
    public void disconnect() {
        try {
            ftpClient.disconnect();
            logger.info("断开FTP连接");
        } catch (IOException e) {
            logger.error("断开FTP连接异常", e);
        }
    }

    /**
     * 判断远程路径下是否存在文件
     * @param ftpPath -
     * @return -
     */
    public boolean isExsits(String ftpPath) {
        if(!isLogin){
            return false;
        }
        try {
            FTPFile[] files =ftpClient.listFiles(ftpPath);
            if(files!=null&&files.length>0){
                System.out.println("files size:"+files[0].getSize());
                return true;
            }else {
                return false;
            }
        } catch (IOException e) {
            logger.error("查询文件["+ftpPath+"]异常", e);
            e.printStackTrace();
            return false;
        }

    }

    /**
     * 删除单个文件
     *
     * @param filePath 文件FTP完整路径
     * @return -
     */
    public boolean deleteFile(String filePath) {
        if(!isLogin){
            return false;
        }
        boolean rs = false;
        String filePath_ = formatPath(filePath);
        try {
            rs = ftpClient.deleteFile(filePath_);
            if (rs) {
                logger.info("删除FTP文件[" + filePath_ + "]");
            }
        } catch (IOException e) {
            logger.error("删除FTP文件[" + filePath_ + "]异常", e);
        }
        return rs;
    }

    /**
     * 删除目录,会自动遍历目录下所有的文件，并删除所有文件和子目录
     *
     * @param pathName 目录FTP完整路径
     * @return  -
     */
    public boolean deleteDir(String pathName) {
        if(!isLogin){
            return false;
        }
        changeToRoot();
        String pathName_ = formatPath(pathName);
        boolean rs = true;
        try {
            //当前待遍历的目录队列
            List<String> list = new ArrayList<>();
            //待删除的目录队列
            List<String> dirList = new ArrayList<>();
            list.add(pathName_);
            dirList.add(pathName_);
            while (list.size() > 0) {
                String path_ = list.remove(0);
                if(!changToExistDir(path_))
                    continue;
                //目录遍历
                FTPFile[] ftpFiles = ftpClient.listFiles();
                for (FTPFile ftpFile : ftpFiles) {
                    String fileName = ftpFile.getName();
                    if (ftpFile.isFile()) {
                        //是文件直接删除
                        if (ftpClient.deleteFile(fileName)) {
                            logger.info("删除FTP文件[" + path_ + pathSeparator + fileName + "]成功");
                        } else {
                            logger.info("删除FTP文件[" + path_ + pathSeparator + fileName + "]失败");
                            rs = false;
                        }
                    } else if (ftpFile.isDirectory()) {
                        //是目录加入待遍历列表和待删除列表
                        list.add(path_ + pathSeparator + fileName);
                        dirList.add(path_ + pathSeparator + fileName);
                    }
                }
                changeToRoot();
            }
            while (dirList.size() > 0) {
                String dirPath = dirList.remove(dirList.size() - 1);
                if (ftpClient.removeDirectory(dirPath)) {
                    logger.info("删除FTP目录[" + dirPath + "]成功");
                } else {
                    logger.info("删除FTP目录[" + dirPath + "]失败");
                    rs = false;
                }
            }
        } catch (IOException e) {
            logger.error("删除FTP目录[" + pathName_ + "]异常", e);
            rs = false;
        }
        return rs;
    }

    /**
     * 从本地上传文件localFile到远程目录ftpPath下
     *
     * @param localFile 本地文件
     * @param ftpPath   远程目录
     */
    public boolean upload(File localFile, String ftpPath) {
        String ftpPath_ = formatPath(ftpPath);
        changeToRoot();
        boolean flag = false;
        if (localFile.exists() && localFile.canRead()) {
            logger.info("开始上传文件[" + localFile + "]到FTP[" + ftpPath_ + "]");
            try (
                    InputStream inputStream = new FileInputStream(localFile)
            ) {
                ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
                if (!changToDir(ftpPath_))
                    return false;
                if (ftpClient.storeFile(localFile.getName(), inputStream)) {
                    logger.info("上传文件[" + localFile.getName() + "]到FTP目录[" + ftpPath_ + "]成功");
                    flag = true;
                } else {
                    logger.info("上传文件[" + localFile.getName() + "]到FTP目录[" + ftpPath_ + "]失败");
                }
            } catch (IOException e) {
                logger.error("上传文件[" + localFile.getName() + "]到FTP目录[" + ftpPath_ + "]异常", e);
            }
        }
        return flag;
    }

    /**
     * 从本地上传文件localFile到远程目录ftpPath下,并命名为fileName
     *
     * @param localFile 本地文件完整路径
     * @param ftpPath   远程目录
     * @param fileName  上传保存的文件名
     */
    public boolean upload(File localFile, String ftpPath, String fileName) {
        String ftpPath_ = formatPath(ftpPath);
        changeToRoot();
        boolean flag = false;
        if (localFile.exists() && localFile.canRead()) {
            logger.info("开始上传文件[" + localFile + "]为FTP[" + ftpPath_ + pathSeparator + fileName + "]");
            try (
                    InputStream inputStream = new FileInputStream(localFile)
            ) {
                ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
                if (!changToDir(ftpPath_))
                    return false;
                flag = ftpClient.storeFile(fileName, inputStream);
                logger.info("上传文件[" + localFile.getName() + "]为FTP[" + ftpPath_ + pathSeparator + fileName + "]成功");
            } catch (IOException e) {
                logger.error("上传文件[" + localFile.getName() + "]为FTP[" + ftpPath_ + pathSeparator + fileName + "]异常", e);
            }
        }
        return flag;
    }

    /**
     * 把字符串content写到Ftp的目录filePath下的fileName文件内，若文件不存在则新建写入。
     *
     * @param content  内容
     * @param filePath 文件目录
     * @param fileName 文件名称
     * @return 是否写入成功
     */
    public boolean writeStringToFtpFile(String content, String filePath, String fileName) {
        boolean b = false;
        String filePath_ = formatPath(filePath);
        try (
                //以指定字符格式写入
                InputStream is = new ByteArrayInputStream(new String(content.getBytes(), ftpEncoding).getBytes());

        ) {
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            ftpClient.changeWorkingDirectory(filePath_);
            b = ftpClient.storeFile(fileName, is);
            logger.info("把字符串" + content + "写到FTP的" + filePath_ + "下的文件" + fileName + "内" + (b ? "成功" : "失败"));
        } catch (IOException e) {
            logger.error("把字符串" + content + "写到FTP的" + filePath_ + "下的文件" + fileName + "内异常", e);
        }
        return b;
    }

    /**
     * 不做任何判断从FTP上下载文件ftpFilePath为本地localFilePath
     *
     * @param ftpFilePath FTP文件
     * @param localFile   本地文件
     * @return -
     */
    private boolean downloadFile_(String ftpFilePath, File localFile) {
        boolean flag = false;
        try (
                FileOutputStream os = new FileOutputStream(localFile)
        ) {
            flag = ftpClient.retrieveFile(ftpFilePath, os);
            logger.info("下载文件[" + ftpFilePath + "]为本地文件[" + localFile.getCanonicalPath() + "]" + flag);
        } catch (IOException e) {
            logger.error("下载文件[" + ftpFilePath + "]为本地文件失败", e);
        }
        return flag;
    }

    /**
     * 从FTP上下载文件为本地文件
     *
     * @param ftpFilePath   FTP上的文件地址
     * @param localFilePath 本地文件地址
     * @return -
     */
    public boolean downloadFile(String ftpFilePath, String localFilePath) {
        changeToRoot();
        File file = new File(localFilePath);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        return downloadFile_(ftpFilePath, file);
    }

    /**
     * 从FTP的目录ftpPath下下载文件fileName到本地目录localpath下
     *
     * @param ftpPath   FTP上的文件目录
     * @param localPath 本地目录
     * @param fileName  文件名
     * @return -
     */
    public boolean downloadFile(String ftpPath, String localPath, String fileName) {
        String ftpPath_ = formatPath(ftpPath);
        changeToRoot();
        File file = new File(localPath);
        if (!file.exists()) {
            file.mkdirs();
        }
        return downloadFile_(ftpPath_ + pathSeparator + fileName, new File(localPath, fileName));
    }

    /**
     * 从FTP的目录ftpPath下所有文件到本地目录localpath下
     *
     * @param ftpPath   FTP上的文件目录
     * @param localPath 本地目录
     * @return -
     */
    public boolean downloadFiles(String ftpPath, String localPath) {
        String ftpPath_ = formatPath(ftpPath);
        changeToRoot();
        boolean flag = true;
        try {
            File file = new File(localPath);
            if (!file.exists()) {
                file.mkdirs();
            }
            FTPFile[] ftpFiles = ftpClient.listFiles(ftpPath_);
            for (FTPFile ftpFile : ftpFiles) {
                if (ftpFile.isFile()) {
                    String fName = ftpFile.getName();
                    if (!downloadFile_(ftpPath_ + pathSeparator + fName, new File(localPath, fName))) {
                        flag = false;
                    }
                }
            }
        } catch (IOException e0) {
            flag = false;
            logger.error("从FTP目录[" + ftpPath_ + "]获取文件列表异常", e0);
        }
        return flag;
    }

    /**
     * 从FTP的目录ftpPath下所有文件目录到本地目录localpath下
     *
     * @param ftpPath   FTP上的文件目录
     * @param localPath 本地目录
     * @return -
     */
    public boolean downloadAllFiles(String ftpPath, String localPath) {
        String ftpPath_ = formatPath(ftpPath);
        changeToRoot();
        boolean flag = true;
        try {
            List<String> ftpDirPaths = new ArrayList<>();
            List<String> localDirPaths = new ArrayList<>();
            ftpDirPaths.add(ftpPath_);
            localDirPaths.add(localPath);
            while (ftpDirPaths.size() > 0) {
                String ftpDirPath = ftpDirPaths.remove(0);
                String localDirPath = localDirPaths.remove(0);
                File file = new File(localDirPath);
                if (!file.exists()) {
                    file.mkdirs();
                }
                FTPFile[] ftpFiles = ftpClient.listFiles(ftpDirPath);
                for (FTPFile ftpFile : ftpFiles) {
                    String fName = ftpFile.getName();
                    if (ftpFile.isDirectory()) {
                        ftpDirPaths.add(ftpDirPath + pathSeparator + fName);
                        localDirPaths.add(localDirPath + pathSeparator + fName);
                    } else {
                        downloadFile_(ftpDirPath + pathSeparator + fName, new File(localDirPath, fName));
                    }
                }
            }
        } catch (IOException e0) {
            flag = false;
            logger.error("从FTP目录[" + ftpPath_ + "]获取文件列表异常", e0);
        }
        return flag;
    }

    /**
     * 格式化路径，去掉多余/，去掉..，去掉尾/
     *
     * @param path -
     * @return -
     */
    private static String formatPath(String path) {
        try {
            return path.replaceAll("//+", String.valueOf(pathSeparator)).replaceAll("[^/]+/\\.\\./|/$", "");
        } catch (Exception e) {
            logger.error("路径[" + path + "]格式化异常");
            return rootPath;
        }
    }

    /**
     * 获取目录ftpPath下所有文件，含子目录文件
     *
     * @param ftpPath 文件的完整路径
     * @param isAll 是否遍历子目录
     * @return 完整路径以及对应的FTPFile
     */
    public Map<String, FTPFile> getAllFiles(String ftpPath,boolean isAll) {
        Map<String, FTPFile> map = new HashMap<>();
        if(!isLogin){
            return map;
        }
        List<String> paths = new ArrayList<>();
        String ftpPath_ = formatPath(ftpPath);
        paths.add(ftpPath_);
        while (paths.size() > 0) {
            String path_tmp = paths.remove(0);
            try {
                if(ftpClient.changeWorkingDirectory(path_tmp)){
                    FTPFile[] files = ftpClient.listFiles();
                    for (FTPFile file : files) {
                        if (file.isDirectory()) {
                            if(isAll){
                                paths.add(path_tmp + pathSeparator + file.getName());
                            }else{
                                logger.warn("路径" + path_tmp + "下含目录" + file.getName());
                            }
                        } else if (file.isFile()) {
                            map.put(path_tmp + pathSeparator + file.getName(), file);
                        } else {
                            logger.warn("路径" + path_tmp + "下含特殊文件" + file.getName());
                        }
                    }
                }
            } catch (IOException e) {
                logger.error("获取FTP路径" + path_tmp + "下的文件异常", e);
            }
        }
        return map;
    }

    /**
     * 获取指定文件pathName的文件内容
     *
     * @param pathName -
     * @return -
     */
    public String getFileContent(String pathName) {
        StringBuilder stringBuilder = new StringBuilder();
        String pathName_ = formatPath(pathName);
        try (
                InputStream is = ftpClient.retrieveFileStream(pathName_);
                BufferedReader br = new BufferedReader(new InputStreamReader(is))
        ) {
            String line;
            while ((line = br.readLine()) != null) {
                stringBuilder.append(line);
            }
        } catch (IOException e) {
            logger.error("FTP文件" + pathName_ + "读取异常", e);
        } finally {
            try {
                /*
                 * 官方说： 完成文件传输必须调用completePendingCommand和检查它的返回值来验证成功。
                 * 如果没有这么做，后续命令可能会出错。 简单来说：completePendingCommand()会一直等 ftp server返回226
                 * Transfer complete,但是ftp server 只要在接受到InputStream 执行close方法是，才会返回。所以要先执行close方法
                 * */
                ftpClient.completePendingCommand();
            } catch (IOException e1) {
                logger.error("FTP文件读取后等待异常", e1);
            }
        }
        return stringBuilder.toString();
    }

    public String getCurrentWorkDir() {
        try {
            return ftpClient.printWorkingDirectory();
        } catch (IOException e) {
            logger.error("获取当前FTP工作目录异常", e);
        }
        return null;
    }

    public FTPClient getFtpClient() {
        return ftpClient;
    }

    public static void main(String[] args) {
//        FtpUtil ftpUtil = new FtpUtil("127.0.0.1",21,"thinkit","thinkit");
//        Map<String,FTPFile> map = ftpUtil.getAllFiles("/");
//        for (String path:map.keySet()){
//            System.out.println(path+"::::::"+map.get(path).getName());
//        }
//        FtpUtil ftpUtil = new FtpUtil("127.0.0.1",21,"thinkit","thinkit");
//        ftpUtil.createDirs("/BJJ/2018/10/30/10/24");
//        ftpUtil.createDirs("/BJJ/2018/10/30/10/24");
//        ftpUtil.createDirs("//BJJ/2018/10/30/10/23");
//        ftpUtil.changeToRoot();
//        ftpUtil.changToDir("/BJJ/2018/10/30/10/24");
//        ftpUtil.changeToRoot();
//        ftpUtil.changToDir("/BJJ/2018/10/30/10/aa");
//        ftpUtil.changeToRoot();
//        File file = new File("F:\\FTP\\布什6.wav");
//        ftpUtil.upload(file,"/BJJ/2018/10/30/10/23");
//        ftpUtil.upload(file,"/BJJ/2018/10/30/10/24");
//        ftpUtil.upload(file,"/BJJ/2018/10/30/10/23","测试改名.wav");

//        ftpUtil.downloadFile("/BJJ/2018/10/30/10/23/布什6.wav","F:\\DX语音质检应用系统\\测试用例\\布什61.wav");
//        ftpUtil.downloadFile("/BJJ/2018/10/30/10/24","F:\\DX语音质检应用系统\\测试用例","布什6.wav");
//        ftpUtil.downloadFiles("/BJJ/2018/10/30/10/23","F:\\DX语音质检应用系统\\测试用例\\10TEST");
//        ftpUtil.downloadAllFiles("/BJJ","F:\\DX语音质检应用系统\\测试用例\\11TEST");
//        ftpUtil.deleteDir("/BJJ");
//        ftpUtil.deleteObsoleteDir(DateFormatUtil.str2Date("2020","yyyy"));
//        ftpUtil.disconnect();
    }

}
